﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Lexer
{
    /// <summary>
    /// A class that Lexes things into LexerTokens.
    /// </summary>
    class Lexer
    {
        private readonly String filename;
        private StreamReader file;
        /// <summary>
        /// The current Character
        /// </summary>
        private char getChar;
        /// <summary>
        /// A list of keywords.
        /// </summary>
        private static String[] keywords = new String[] { "if", "subscript", "event", "else", "set" };
        /// <summary>
        ///  A list of symbols.
        /// </summary>
        private static char[] symbols = new char[] { '{', '}', '[', ']', '(', ')', '"', ';' };
        /// <summary>
        /// A list of operators.
        /// </summary>
        private static char[] operators = new char[] { '=', '%', '-', '+', '/', '*', '$', '!', '&'};
        /// <summary>
        /// The current token.
        /// </summary>
        private LexerToken currentToken;
        private bool isClosed;
        public LexerToken CurrentToken
        {
            get { return this.currentToken; }
        }
        public String Filename
        {
            get { return filename; }
        }

        public Lexer(String filename)
        {
            this.isClosed = false;
            this.filename = filename;
            this.file = new StreamReader(filename);
            this.getChar = ' ';
            this.NextToken();
        }
        public delegate bool TestFunction(char c);
        /// <summary>
        /// Gets the next Token, returns, and sets it to currentToken.
        /// </summary>
        /// <returns></returns>
        public LexerToken NextToken()
        {
            if (this.isClosed)
                throw new Exception("File is closed.");
            this.currentToken = this.ReadToken();
            return this.currentToken;
        }
        /// <summary>
        /// Read the next Token from stream.
        /// </summary>
        /// <returns></returns>
        private LexerToken ReadToken()
        {
            // check for EOF
            if(this.file.EndOfStream)
                return new LexerToken(TokenType.END_OF_FILE, "");
            // Readthrough whitespace
            this.ReadWhile(Char.IsWhiteSpace);

            String cur = "";
            if (Char.IsLetter(this.getChar))
            {
                // read through the rest of the letters
                cur = this.ReadWhile(Char.IsLetterOrDigit);
                // keyword if it is registered, ident if not
                if (Lexer.keywords.Contains(cur))
                    return new LexerToken(TokenType.KEYWORD, cur);
                else
                    return new LexerToken(TokenType.IDENTIFIER, cur);
            }
            else if (Char.IsDigit(this.getChar))
            {
                // reads a number literal
                cur = this.ReadWhile(Char.IsDigit);
                return new LexerToken(TokenType.NUMBER_LITERAL, Int32.Parse(cur));
            } else if (Lexer.IsOperator(this.getChar))
            {
                // reads an operator
                cur = this.ReadWhile(Lexer.IsOperator);
                return new LexerToken(TokenType.OPCODE, cur);
            }
            else if (Lexer.IsSymbol(this.getChar))
            {
                cur = this.ReadOne();
                // reads depending on the symbol
                switch (cur)
                {
                    case "{":
                        return new LexerToken(TokenType.OPEN_BRACE, "{");
                    case "}":
                        return new LexerToken(TokenType.CLOSE_BRACE, "}");
                    case "[":
                        // reads the till the next ] which specifies a property
                        cur = this.ReadWhile(Lexer.IsNotCloseBracket);
                        if(!Lexer.IsNotCloseBracket(this.getChar))
                            this.getChar = (char)this.file.Read();
                        return new LexerToken(TokenType.PROPERTY, cur);
                    case "(":
                        return new LexerToken(TokenType.OPEN_PAREN, "(");
                    case ")":
                        return new LexerToken(TokenType.CLOSE_PAREN, ")");
                    case ";":
                        return new LexerToken(TokenType.END_LINE, ";");
                    case "\"":
                        // reads till the next " which specifies a string
                        cur = this.ReadWhile(Lexer.IsNotQuote);
                        if (!Lexer.IsNotQuote(this.getChar))
                            this.getChar = (char)this.file.Read();
                        return new LexerToken(TokenType.STRING_LITERAL, cur);
                    default:
                        throw new Exception("Lexer error.");
                }
            } else if(this.file.EndOfStream)
                return new LexerToken(TokenType.END_OF_FILE, "");

            return null;
        }
        /// <summary>
        /// Read a single character.
        /// </summary>
        /// <returns></returns>
        private string ReadOne()
        {
            String ret = "" + this.getChar;
            this.getChar = (char)this.file.Read();
            return ret;
        }
        /// <summary>
        /// Checks to see if the character is an oeprator.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static bool IsOperator(char c)
        {
            return Char.IsSymbol(c) || Lexer.operators.Contains(c);
        }
        /// <summary>
        /// Checks to see if the character is not a quote.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static bool IsNotQuote(char c)
        {
            return c != '"';
        }
        /// <summary>
        /// Checks to see if its not a close square bracket.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static bool IsNotCloseBracket(char c)
        {
            return c != ']';
        }
        /// <summary>
        /// Checks to see if its a registered symbol.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static bool IsSymbol(char c)
        {
            return Lexer.symbols.Contains(c);
        }
        /// <summary>
        /// Reads while the current character passes the TestFunction.
        /// </summary>
        /// <param name="test"></param>
        /// <returns></returns>
        public String ReadWhile(TestFunction test)
        {
            String ret = "";
            while(test(this.getChar))
            {
                ret += this.getChar;
                this.getChar = (char)this.file.Read();
            }
            return ret;
        }
        /// <summary>
        /// Closes the file.
        /// </summary>
        public void Close()
        {
            this.file.Close();
            this.isClosed = true;
        }
    }
}
